Plotting in 3D has many useful applications. This notebook will illustrate some techniques used to create 3D plots in R. There are numerous resources that can be used for this purpose.
Preliminaries
Load the packages used in this notebook. packr contains a sample data set, tidyverse is a collection of packages for data manipulation and visualization, plot3D is a package for creating 3D plots, plot3Drgl is a package to convert plot3D static plots into interactive plots, and plotly is a package for creating interactive plots:
library(packr)
library(tidyverse)
library(plot3D)
library(plotly)
Summon the data set:
data(energy_and_emissions)
Quickly review the contents of the data set:
summary(energy_and_emissions)
Country Area Population PYear GDPPC bblpd EYear
Length:188 Min. : 54 Min. :5.292e+03 JULY 2017 EST.:188 Min. : 145 Min. : 400 2014:148
Class :character 1st Qu.: 25618 1st Qu.:1.955e+06 1st Qu.: 1817 1st Qu.: 13000 2015: 34
Mode :character Median : 113098 Median :8.101e+06 Median : 5620 Median : 53000 2016: 6
Mean : 666756 Mean :3.847e+07 Mean : 13572 Mean : 490635
3rd Qu.: 479278 3rd Qu.:2.558e+07 3rd Qu.: 16148 3rd Qu.: 255500
Max. :16377742 Max. :1.379e+09 Max. :100161 Max. :19530000
CO2_1995 CO2_2005 CO2_2015 Continent
Min. : 12 Min. : 14 Min. : 28 Africa :51
1st Qu.: 936 1st Qu.: 1379 1st Qu.: 2153 Americas:41
Median : 6661 Median : 8434 Median : 10062 Asia :47
Mean : 121256 Mean : 152549 Mean : 184978 Europe :37
3rd Qu.: 61534 3rd Qu.: 62283 3rd Qu.: 75294 Oceania :12
Max. :5294648 Max. :6174717 Max. :10641789
Some variables are absolute values (e.g., population) and others are rates (e.g., GDP per capita). Mutate the dataframe to obain variables for energy consumption and emissions that are rates:
energy_and_emissions <- energy_and_emissions %>%
mutate(GDPPC = GDPPC / 100000,
EPC = bblpd/Population,
CO2PC_1995 = CO2_1995/Population,
CO2PC_2005 = CO2_2005/Population,
CO2PC_2015 = CO2_2015/Population)
Pseudo-3D plots
The first thing to note is that any plots rendered on a flat surface such as a page in a book or a computer screen are pseudo-3D in the sense that they only exist in 2D. That said, there are different ways of representing 3 and even higher dimensions in a 2D surface.
We will begin with some plots that are more pseudo-3D than others.
Perhaps the simplest way to represent a 3D plot in two dimensions is by projecting one of the dimensions on the other two, effectively flattening it but in such a way that an aspect of the 3D is still legible from the plot.
Consider the energy_and_emissions data set. It contains several variables, including population GDPPC, oil consumption in barrels of oil per day, and \(CO_2\) emissions.
ggplot(data = energy_and_emissions, aes(x = GDPPC, y = CO2PC_1995)) + geom_point()

ggplot(data = energy_and_emissions, aes(x = EPC, y = CO2PC_1995)) + geom_point()

ggplot(data = energy_and_emissions, aes(x = GDPPC, y = EPC, color = CO2PC_1995)) +
geom_point() + scale_color_viridis_c(direction = -1)

In this plot, we “flatten” the variable for emissions, projecting it on the 2D plane of GDP per capita and energy consumptions per capita, and represent the “height” by means of colors. We can further emphasize the “height” of the emissions variable (its implied projection on the z axis) by further manipulating the attributes of the geometric objects, for instance their size:
ggplot(data = energy_and_emissions, aes(x = GDPPC, y = EPC, color = CO2PC_1995, size = CO2PC_1995)) +
geom_point() +
scale_color_viridis_c(direction = -1)

With some imagination, you can maybe visualize in your mind the scatter of points on the flattened third-dimension (the z-axis).
3D plots with plot3D
GDPPC <- energy_and_emissions$GDPPC
EPC <- energy_and_emissions$EPC
CO2PC_1995 <- energy_and_emissions$CO2PC_1995
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995)

Color palettes
There are preset palettes (the default is jet.col(n) with n, the number of colors to generate, set to 100). Alternatives include: jet2.col(n). This palette is similar to jet.col but brighter, since it lacks the deeper blues and reds:
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
col = jet2.col(n = 100))

Palette gg.col(n) uses colors similar to those used in ggplot2:
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
col = gg.col(n = 50))

The function ramp.col() creates a sequence of colors by interpolation, and is based on two or three colors, for example:
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
col = ramp.col(col = c("green", "red"), n = 50, alpha = 1))

And with three colors:
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
col = ramp.col(col = c("blue", "yellow", "red"), n = 50, alpha = 1))

Aspect of plot and annotations
The shape of the markers can be changed by means of the argument pch. Here is a list of symbols available for plotting, The size of the symbol is controled by the argument cex:
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
pch = 0,
cex = 1.5)

We can also add annotations, such as labels and a legend:
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
pch = 20,
cex = 1.5,
xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"))
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
pch = 20,
cex = 1.5,
labels = row.names(energy_and_emissions),
xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"))
text3D(x = GDPPC, y = EPC, z = CO2PC_1995,
cex = 0.75,
labels = energy_and_emissions$Continent,
add = TRUE)

Changing the perspective
Two arguments control the viewing direction: theta and phi. These two are angles, theta is the angle with respect to the azimuth, whereas phi is the colatitude. The default values are \(\theta = 40\) and \(\phi = 40\) (in degrees).
We can see the effect of changing these values. For instance, if we change the value of theta, the perspective rotates with respect to the z-axis:
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
pch = 20,
cex = 1.5,
theta = 0,
labels = row.names(energy_and_emissions),
xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"))

If we change the value of phi, the perspective rotates with respect to the x-y plane:
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
pch = 20,
cex = 1.5,
theta = 40,
phi = 0,
labels = row.names(energy_and_emissions),
xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"))

Interactive 3D plots with plot3Drgl
Package plot3Drgl can be used to create interactive plots based on objects created with plot3D. The function plotrgl() will create an interactive version of the most recent plot3D object:
scatter_labels <- scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
pch = 20,
cex = 1.5,
labels = row.names(energy_and_emissions),
xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"))
text3D(x = GDPPC, y = EPC, z = CO2PC_1995,
cex = 0.75,
labels = energy_and_emissions$Continent,
add = TRUE)
plotrgl()
Other 3D objects
There are other types of objects (besides points and text) that can be plotted in 3D using plot3D, including:
- 3D lines
- 3D ribbons
- 3D slices
- 3D surfaces
Compute the linear regression (z = ax + by + d)
mod <- lm(CO2PC_1995 ~ GDPPC + EPC)
summary(mod)
Call:
lm(formula = CO2PC_1995 ~ GDPPC + EPC)
Residuals:
Min 1Q Median 3Q Max
-0.0074792 -0.0012215 -0.0009659 0.0008195 0.0097414
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.0011333 0.0002589 4.378 2.00e-05 ***
GDPPC 0.0105080 0.0013890 7.565 1.76e-12 ***
EPC 0.0374944 0.0088802 4.222 3.79e-05 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.002753 on 185 degrees of freedom
Multiple R-squared: 0.5043, Adjusted R-squared: 0.4989
F-statistic: 94.1 on 2 and 185 DF, p-value: < 2.2e-16
Predict values on regular xy grid
grid.lines = 40
GDPPC.pred <- seq(min(GDPPC), max(GDPPC), length.out = grid.lines)
EPC.pred <- seq(min(EPC), max(EPC), length.out = grid.lines)
xy <- expand.grid(GDPPC = GDPPC.pred, EPC = EPC.pred)
CO2.pred <- matrix(predict(mod, newdata = xy),
nrow = grid.lines, ncol = grid.lines)
Fitted points for droplines to surface
fitpoints <- predict(mod)
Scatter plot with regression plane
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
pch = 20,
cex = 1.5,
theta = 30,
phi = 20,
labels = row.names(energy_and_emissions),
xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"),
surf = list(x = GDPPC.pred,
y = EPC.pred,
z = CO2.pred,
facets = NA,
fit = fitpoints))

scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
pch = 20,
cex = 1.5,
theta = 30,
phi = 20,
labels = row.names(energy_and_emissions),
xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"),
surf = list(x = GDPPC.pred,
y = EPC.pred,
z = CO2.pred,
facets = NA,
fit = fitpoints))
text3D(x = GDPPC, y = EPC, z = CO2PC_1995,
cex = 0.75,
labels = energy_and_emissions$Country,
add = TRUE)

plotrgl()
Interactive 3D plots with plotly
colors <- c('#4AC6B7', '#1972A4', '#965F8A', '#FF7070', '#C61951')
plot_ly(energy_and_emissions, x = ~GDPPC, y = ~EPC, z = ~CO2PC_1995, color = ~Continent, size = ~CO2PC_1995, colors = colors,
marker = list(symbol = 'circle', sizemode = 'diameter',
line = list(width = 2, color = '#000000')),
sizes = c(5, 40),
text = ~paste('Country:', Country,
'<br>GDP per capita:', round(GDPPC, 3),
'<br>Energy Consumption:', round(EPC,3),
'<br>CO2 emmissions 1995:', round(CO2PC_1995, 3))) %>%
layout(title = 'GDP per capita v. Energy consumption per capita',
scene = list(xaxis = list(title = 'GDP per capita',
gridcolor = 'rgb(255, 255, 255)',
zerolinewidth = 1,
ticklen = 5,
gridwidth = 2),
yaxis = list(title = 'Energy Consumption',
gridcolor = 'rgb(255, 255, 255)',
zerolinewidth = 1,
ticklen = 5,
gridwith = 2),
zaxis = list(title = 'CO2 Emissions',
gridcolor = 'rgb(255, 255, 255)',
zerolinewidth = 1,
ticklen = 5,
gridwith = 2)),
paper_bgcolor = 'rgb(243, 243, 243)',
plot_bgcolor = 'rgb(243, 243, 243)')
co2_95to15 <- energy_and_emissions %>%
dplyr::select(Country, Continent, GDPPC, EPC, CO2PC_1995, CO2PC_2005, CO2PC_2015) %>% # First select relevant variables
gather(Year, CO2, -c(Country, Continent, GDPPC, EPC)) %>% # Gather CO2 columns: it is important to exclude from this operation the columns Country and GDP
mutate(Year = factor(Year,
levels = c("CO2PC_1995", "CO2PC_2005", "CO2PC_2015"),
labels = c("1995", "2005", "2015"))) # Relabel the years
plot_ly(co2_95to15, x = ~GDPPC, y = ~EPC, z = ~CO2,
color = ~Continent,
size = ~CO2,
colors = colors,
frame = ~Year,
marker = list(symbol = 'circle', sizemode = 'diameter',
line = list(width = 2, color = '#000000')),
sizes = c(5, 40),
text = ~paste('Country:', Country,
'<br>GDP per capita:', round(GDPPC, 3),
'<br>Energy Consumption:', round(EPC,3),
'<br>CO2 emmissions 1995:', round(CO2PC_1995, 3))) %>%
layout(title = 'GDP per capita v. Energy consumption per capita',
scene = list(xaxis = list(title = 'GDP per capita',
gridcolor = 'rgb(255, 255, 255)',
zerolinewidth = 1,
ticklen = 5,
gridwidth = 2),
yaxis = list(title = 'Energy Consumption',
gridcolor = 'rgb(255, 255, 255)',
zerolinewidth = 1,
ticklen = 5,
gridwith = 2),
zaxis = list(title = 'CO2 Emissions',
gridcolor = 'rgb(255, 255, 255)',
range = c(0, 0.1),
zerolinewidth = 1,
ticklen = 5,
gridwith = 2)),
paper_bgcolor = 'rgb(243, 243, 243)',
plot_bgcolor = 'rgb(243, 243, 243)') %>%
animation_opts(
1300, redraw = FALSE
)
LS0tDQp0aXRsZTogIjNEIFBsb3R0aW5nIGluIFI6IEV4YW1wbGVzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KUGxvdHRpbmcgaW4gM0QgaGFzIG1hbnkgdXNlZnVsIGFwcGxpY2F0aW9ucy4gVGhpcyBub3RlYm9vayB3aWxsIGlsbHVzdHJhdGUgc29tZSB0ZWNobmlxdWVzIHVzZWQgdG8gY3JlYXRlIDNEIHBsb3RzIGluIGBSYC4gVGhlcmUgYXJlIG51bWVyb3VzIHJlc291cmNlcyB0aGF0IGNhbiBiZSB1c2VkIGZvciB0aGlzIHB1cnBvc2UuIA0KDQojIyBQcmVsaW1pbmFyaWVzDQoNCkxvYWQgdGhlIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyBub3RlYm9vay4gYHBhY2tyYCBjb250YWlucyBhIHNhbXBsZSBkYXRhIHNldCwgYHRpZHl2ZXJzZWAgaXMgYSBjb2xsZWN0aW9uIG9mIHBhY2thZ2VzIGZvciBkYXRhIG1hbmlwdWxhdGlvbiBhbmQgdmlzdWFsaXphdGlvbiwgYHBsb3QzRGAgaXMgYSBwYWNrYWdlIGZvciBjcmVhdGluZyAzRCBwbG90cywgYHBsb3QzRHJnbGAgaXMgYSBwYWNrYWdlIHRvIGNvbnZlcnQgYHBsb3QzRGAgc3RhdGljIHBsb3RzIGludG8gaW50ZXJhY3RpdmUgcGxvdHMsIGFuZCBgcGxvdGx5YCBpcyBhIHBhY2thZ2UgZm9yIGNyZWF0aW5nIGludGVyYWN0aXZlIHBsb3RzOg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkocGFja3IpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocGxvdDNEKQ0KbGlicmFyeShwbG90M0RyZ2wpDQpsaWJyYXJ5KHBsb3RseSkNCmBgYA0KDQpTdW1tb24gdGhlIGRhdGEgc2V0Og0KYGBge3J9DQpkYXRhKGVuZXJneV9hbmRfZW1pc3Npb25zKQ0KYGBgDQoNClF1aWNrbHkgcmV2aWV3IHRoZSBjb250ZW50cyBvZiB0aGUgZGF0YSBzZXQ6DQpgYGB7cn0NCnN1bW1hcnkoZW5lcmd5X2FuZF9lbWlzc2lvbnMpDQpgYGANCg0KU29tZSB2YXJpYWJsZXMgYXJlIGFic29sdXRlIHZhbHVlcyAoZS5nLiwgcG9wdWxhdGlvbikgYW5kIG90aGVycyBhcmUgcmF0ZXMgKGUuZy4sIEdEUCBwZXIgY2FwaXRhKS4gTXV0YXRlIHRoZSBkYXRhZnJhbWUgdG8gb2JhaW4gdmFyaWFibGVzIGZvciBlbmVyZ3kgY29uc3VtcHRpb24gYW5kIGVtaXNzaW9ucyB0aGF0IGFyZSByYXRlczoNCmBgYHtyfQ0KZW5lcmd5X2FuZF9lbWlzc2lvbnMgPC0gZW5lcmd5X2FuZF9lbWlzc2lvbnMgJT4lDQogIG11dGF0ZShHRFBQQyA9IEdEUFBDIC8gMTAwMDAwLA0KICAgICAgICAgRVBDID0gYmJscGQvUG9wdWxhdGlvbiwgDQogICAgICAgICBDTzJQQ18xOTk1ID0gQ08yXzE5OTUvUG9wdWxhdGlvbiwgDQogICAgICAgICBDTzJQQ18yMDA1ID0gQ08yXzIwMDUvUG9wdWxhdGlvbiwgDQogICAgICAgICBDTzJQQ18yMDE1ID0gQ08yXzIwMTUvUG9wdWxhdGlvbikNCmBgYA0KDQojIyBQc2V1ZG8tM0QgcGxvdHMNCg0KVGhlIGZpcnN0IHRoaW5nIHRvIG5vdGUgaXMgdGhhdCBhbnkgcGxvdHMgcmVuZGVyZWQgb24gYSBmbGF0IHN1cmZhY2Ugc3VjaCBhcyBhIHBhZ2UgaW4gYSBib29rIG9yIGEgY29tcHV0ZXIgc2NyZWVuIGFyZSBwc2V1ZG8tM0QgaW4gdGhlIHNlbnNlIHRoYXQgdGhleSBvbmx5IGV4aXN0IGluIDJELiBUaGF0IHNhaWQsIHRoZXJlIGFyZSBkaWZmZXJlbnQgd2F5cyBvZiByZXByZXNlbnRpbmcgMyBhbmQgZXZlbiBoaWdoZXIgZGltZW5zaW9ucyBpbiBhIDJEIHN1cmZhY2UuDQoNCldlIHdpbGwgYmVnaW4gd2l0aCBzb21lIHBsb3RzIHRoYXQgYXJlIG1vcmUgcHNldWRvLTNEIHRoYW4gb3RoZXJzLg0KDQpQZXJoYXBzIHRoZSBzaW1wbGVzdCB3YXkgdG8gcmVwcmVzZW50IGEgM0QgcGxvdCBpbiB0d28gZGltZW5zaW9ucyBpcyBieSBfcHJvamVjdGluZ18gb25lIG9mIHRoZSBkaW1lbnNpb25zIG9uIHRoZSBvdGhlciB0d28sIGVmZmVjdGl2ZWx5IGZsYXR0ZW5pbmcgaXQgYnV0IGluIHN1Y2ggYSB3YXkgdGhhdCBhbiBhc3BlY3Qgb2YgdGhlIDNEIGlzIHN0aWxsIGxlZ2libGUgZnJvbSB0aGUgcGxvdC4NCg0KQ29uc2lkZXIgdGhlIGBlbmVyZ3lfYW5kX2VtaXNzaW9uc2AgZGF0YSBzZXQuIEl0IGNvbnRhaW5zIHNldmVyYWwgdmFyaWFibGVzLCBpbmNsdWRpbmcgcG9wdWxhdGlvbiBHRFBQQywgb2lsIGNvbnN1bXB0aW9uIGluIGJhcnJlbHMgb2Ygb2lsIHBlciBkYXksIGFuZCAkQ09fMiQgZW1pc3Npb25zLg0KDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBlbmVyZ3lfYW5kX2VtaXNzaW9ucywgYWVzKHggPSBHRFBQQywgeSA9IENPMlBDXzE5OTUpKSArIGdlb21fcG9pbnQoKQ0KYGBgDQoNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGVuZXJneV9hbmRfZW1pc3Npb25zLCBhZXMoeCA9IEVQQywgeSA9IENPMlBDXzE5OTUpKSArIGdlb21fcG9pbnQoKQ0KYGBgDQoNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGVuZXJneV9hbmRfZW1pc3Npb25zLCBhZXMoeCA9IEdEUFBDLCB5ID0gRVBDLCBjb2xvciA9IENPMlBDXzE5OTUpKSArIA0KICBnZW9tX3BvaW50KCkgKyANCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKGRpcmVjdGlvbiA9IC0xKQ0KYGBgDQoNCkluIHRoaXMgcGxvdCwgd2UgImZsYXR0ZW4iIHRoZSB2YXJpYWJsZSBmb3IgZW1pc3Npb25zLCBwcm9qZWN0aW5nIGl0IG9uIHRoZSAyRCBwbGFuZSBvZiBHRFAgcGVyIGNhcGl0YSBhbmQgZW5lcmd5IGNvbnN1bXB0aW9ucyBwZXIgY2FwaXRhLCBhbmQgcmVwcmVzZW50IHRoZSAiaGVpZ2h0IiBieSBtZWFucyBvZiBjb2xvcnMuIFdlIGNhbiBmdXJ0aGVyIGVtcGhhc2l6ZSB0aGUgImhlaWdodCIgb2YgdGhlIGVtaXNzaW9ucyB2YXJpYWJsZSAoaXRzIGltcGxpZWQgcHJvamVjdGlvbiBvbiB0aGUgeiBheGlzKSBieSBmdXJ0aGVyIG1hbmlwdWxhdGluZyB0aGUgYXR0cmlidXRlcyBvZiB0aGUgZ2VvbWV0cmljIG9iamVjdHMsIGZvciBpbnN0YW5jZSB0aGVpciBzaXplOg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGVuZXJneV9hbmRfZW1pc3Npb25zLCBhZXMoeCA9IEdEUFBDLCB5ID0gRVBDLCBjb2xvciA9IENPMlBDXzE5OTUsIHNpemUgPSBDTzJQQ18xOTk1KSkgKyANCiAgZ2VvbV9wb2ludCgpICsgDQogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhkaXJlY3Rpb24gPSAtMSkNCmBgYA0KDQpXaXRoIHNvbWUgaW1hZ2luYXRpb24sIHlvdSBjYW4gbWF5YmUgdmlzdWFsaXplIGluIHlvdXIgbWluZCB0aGUgc2NhdHRlciBvZiBwb2ludHMgb24gdGhlIGZsYXR0ZW5lZCB0aGlyZC1kaW1lbnNpb24gKHRoZSB6LWF4aXMpLg0KDQojIyAzRCBwbG90cyB3aXRoIGBwbG90M0RgDQoNCmBgYHtyfQ0KR0RQUEMgPC0gZW5lcmd5X2FuZF9lbWlzc2lvbnMkR0RQUEMNCkVQQyA8LSBlbmVyZ3lfYW5kX2VtaXNzaW9ucyRFUEMNCkNPMlBDXzE5OTUgPC0gZW5lcmd5X2FuZF9lbWlzc2lvbnMkQ08yUENfMTk5NQ0KYGBgDQoNCg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSkNCmBgYA0KDQojIyMgQ29sb3IgcGFsZXR0ZXMNCg0KVGhlcmUgYXJlIHByZXNldCBwYWxldHRlcyAodGhlIGRlZmF1bHQgaXMgYGpldC5jb2wobilgIHdpdGggYG5gLCB0aGUgbnVtYmVyIG9mIGNvbG9ycyB0byBnZW5lcmF0ZSwgc2V0IHRvIDEwMCkuIEFsdGVybmF0aXZlcyBpbmNsdWRlOiBgamV0Mi5jb2wobilgLiBUaGlzIHBhbGV0dGUgaXMgc2ltaWxhciB0byBgamV0LmNvbGAgYnV0IGJyaWdodGVyLCBzaW5jZSBpdCBsYWNrcyB0aGUgZGVlcGVyIGJsdWVzIGFuZCByZWRzOg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwNCiAgICAgICAgICBjb2wgPSBqZXQyLmNvbChuID0gMTAwKSkNCmBgYA0KDQpQYWxldHRlIGBnZy5jb2wobilgIHVzZXMgY29sb3JzIHNpbWlsYXIgdG8gdGhvc2UgdXNlZCBpbiBgZ2dwbG90MmA6DQpgYGB7cn0NCnNjYXR0ZXIzRCh4ID0gR0RQUEMsIHkgPSBFUEMsIHogPSBDTzJQQ18xOTk1LA0KICAgICAgICAgIGNvbCA9IGdnLmNvbChuID0gNTApKQ0KYGBgDQoNClRoZSBmdW5jdGlvbiBgcmFtcC5jb2woKWAgY3JlYXRlcyBhIHNlcXVlbmNlIG9mIGNvbG9ycyBieSBpbnRlcnBvbGF0aW9uLCBhbmQgaXMgYmFzZWQgb24gdHdvIG9yIHRocmVlIGNvbG9ycywgZm9yIGV4YW1wbGU6DQpgYGB7cn0NCnNjYXR0ZXIzRCh4ID0gR0RQUEMsIHkgPSBFUEMsIHogPSBDTzJQQ18xOTk1LA0KICAgICAgICAgIGNvbCA9IHJhbXAuY29sKGNvbCA9IGMoImdyZWVuIiwgInJlZCIpLCBuID0gNTAsIGFscGhhID0gMSkpDQpgYGANCg0KQW5kIHdpdGggdGhyZWUgY29sb3JzOg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwNCiAgICAgICAgICBjb2wgPSByYW1wLmNvbChjb2wgPSBjKCJibHVlIiwgInllbGxvdyIsICJyZWQiKSwgbiA9IDUwLCBhbHBoYSA9IDEpKQ0KYGBgDQoNCiMjIyBBc3BlY3Qgb2YgcGxvdCBhbmQgYW5ub3RhdGlvbnMNCg0KVGhlIHNoYXBlIG9mIHRoZSBtYXJrZXJzIGNhbiBiZSBjaGFuZ2VkIGJ5IG1lYW5zIG9mIHRoZSBhcmd1bWVudCBgcGNoYC4gSGVyZSBpcyBhIFtsaXN0IG9mIHN5bWJvbHNdKGh0dHA6Ly93d3cuc3RoZGEuY29tL2VuZ2xpc2gvd2lraS9yLXBsb3QtcGNoLXN5bWJvbHMtdGhlLWRpZmZlcmVudC1wb2ludC1zaGFwZXMtYXZhaWxhYmxlLWluLXIpIGF2YWlsYWJsZSBmb3IgcGxvdHRpbmcsIFRoZSBzaXplIG9mIHRoZSBzeW1ib2wgaXMgY29udHJvbGVkIGJ5IHRoZSBhcmd1bWVudCBgY2V4YDoNCmBgYHtyfQ0Kc2NhdHRlcjNEKHggPSBHRFBQQywgeSA9IEVQQywgeiA9IENPMlBDXzE5OTUsIA0KICAgICAgICAgIHBjaCA9IDAsDQogICAgICAgICAgY2V4ID0gMS41KQ0KYGBgDQoNCldlIGNhbiBhbHNvIGFkZCBhbm5vdGF0aW9ucywgc3VjaCBhcyBsYWJlbHMgYW5kIGEgbGVnZW5kOg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgICAgcGNoID0gMjAsDQogICAgICAgICAgY2V4ID0gMS41LA0KICAgICAgICAgIHhsYWIgPSAiR0RQUEMiLCB5bGFiID0gIkVQQyIsIHpsYWIgPSAiQ08yIiwNCiAgICAgICAgICBjbGFiID0gYygiQ08yIEVtaXNzaW9ucyIsICJwZXIgQ2FwaXRhIiwgIihraWxvdG9ubmVzKSIpKQ0KYGBgDQoNCmBgYHtyfQ0Kc2NhdHRlcjNEKHggPSBHRFBQQywgeSA9IEVQQywgeiA9IENPMlBDXzE5OTUsIA0KICAgICAgICAgIHBjaCA9IDIwLA0KICAgICAgICAgIGNleCA9IDEuNSwNCiAgICAgICAgICBsYWJlbHMgPSByb3cubmFtZXMoZW5lcmd5X2FuZF9lbWlzc2lvbnMpLA0KICAgICAgICAgIHhsYWIgPSAiR0RQUEMiLCB5bGFiID0gIkVQQyIsIHpsYWIgPSAiQ08yIiwNCiAgICAgICAgICBjbGFiID0gYygiQ08yIEVtaXNzaW9ucyIsICJwZXIgQ2FwaXRhIiwgIihraWxvdG9ubmVzKSIpKQ0KDQp0ZXh0M0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgY2V4ID0gMC43NSwNCiAgICAgICBsYWJlbHMgPSBlbmVyZ3lfYW5kX2VtaXNzaW9ucyRDb250aW5lbnQsDQogICAgICAgYWRkID0gVFJVRSkNCmBgYA0KDQojIyMgQ2hhbmdpbmcgdGhlIHBlcnNwZWN0aXZlDQoNClR3byBhcmd1bWVudHMgY29udHJvbCB0aGUgdmlld2luZyBkaXJlY3Rpb246IGB0aGV0YWAgYW5kIGBwaGlgLiBUaGVzZSB0d28gYXJlIGFuZ2xlcywgYHRoZXRhYCBpcyB0aGUgYW5nbGUgd2l0aCByZXNwZWN0IHRvIHRoZSBhemltdXRoLCB3aGVyZWFzIGBwaGlgIGlzIHRoZSBjb2xhdGl0dWRlLiBUaGUgZGVmYXVsdCB2YWx1ZXMgYXJlICRcdGhldGEgPSA0MCQgYW5kICRccGhpID0gNDAkIChpbiBkZWdyZWVzKS4NCg0KV2UgY2FuIHNlZSB0aGUgZWZmZWN0IG9mIGNoYW5naW5nIHRoZXNlIHZhbHVlcy4gRm9yIGluc3RhbmNlLCBpZiB3ZSBjaGFuZ2UgdGhlIHZhbHVlIG9mIGB0aGV0YWAsIHRoZSBwZXJzcGVjdGl2ZSByb3RhdGVzIHdpdGggcmVzcGVjdCB0byB0aGUgei1heGlzOg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgICAgcGNoID0gMjAsDQogICAgICAgICAgY2V4ID0gMS41LA0KICAgICAgICAgIHRoZXRhID0gMCwNCiAgICAgICAgICBsYWJlbHMgPSByb3cubmFtZXMoZW5lcmd5X2FuZF9lbWlzc2lvbnMpLA0KICAgICAgICAgIHhsYWIgPSAiR0RQUEMiLCB5bGFiID0gIkVQQyIsIHpsYWIgPSAiQ08yIiwNCiAgICAgICAgICBjbGFiID0gYygiQ08yIEVtaXNzaW9ucyIsICJwZXIgQ2FwaXRhIiwgIihraWxvdG9ubmVzKSIpKQ0KYGBgDQoNCklmIHdlIGNoYW5nZSB0aGUgdmFsdWUgb2YgYHBoaWAsIHRoZSBwZXJzcGVjdGl2ZSByb3RhdGVzIHdpdGggcmVzcGVjdCB0byB0aGUgeC15IHBsYW5lOg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgICAgcGNoID0gMjAsDQogICAgICAgICAgY2V4ID0gMS41LA0KICAgICAgICAgIHRoZXRhID0gNDAsDQogICAgICAgICAgcGhpID0gMCwNCiAgICAgICAgICBsYWJlbHMgPSByb3cubmFtZXMoZW5lcmd5X2FuZF9lbWlzc2lvbnMpLA0KICAgICAgICAgIHhsYWIgPSAiR0RQUEMiLCB5bGFiID0gIkVQQyIsIHpsYWIgPSAiQ08yIiwNCiAgICAgICAgICBjbGFiID0gYygiQ08yIEVtaXNzaW9ucyIsICJwZXIgQ2FwaXRhIiwgIihraWxvdG9ubmVzKSIpKQ0KYGBgDQoNCiMjIyBJbnRlcmFjdGl2ZSAzRCBwbG90cyB3aXRoIGBwbG90M0RyZ2xgDQoNClBhY2thZ2UgYHBsb3QzRHJnbGAgY2FuIGJlIHVzZWQgdG8gY3JlYXRlIGludGVyYWN0aXZlIHBsb3RzIGJhc2VkIG9uIG9iamVjdHMgY3JlYXRlZCB3aXRoIGBwbG90M0RgLiBUaGUgZnVuY3Rpb24gYHBsb3RyZ2woKWAgd2lsbCBjcmVhdGUgYW4gaW50ZXJhY3RpdmUgdmVyc2lvbiBvZiB0aGUgbW9zdCByZWNlbnQgYHBsb3QzRGAgb2JqZWN0Og0KYGBge3IgZXZhbD1GQUxTRX0NCnNjYXR0ZXJfbGFiZWxzIDwtIHNjYXR0ZXIzRCh4ID0gR0RQUEMsIHkgPSBFUEMsIHogPSBDTzJQQ18xOTk1LCANCiAgICAgICAgICBwY2ggPSAyMCwNCiAgICAgICAgICBjZXggPSAxLjUsDQogICAgICAgICAgbGFiZWxzID0gcm93Lm5hbWVzKGVuZXJneV9hbmRfZW1pc3Npb25zKSwNCiAgICAgICAgICB4bGFiID0gIkdEUFBDIiwgeWxhYiA9ICJFUEMiLCB6bGFiID0gIkNPMiIsDQogICAgICAgICAgY2xhYiA9IGMoIkNPMiBFbWlzc2lvbnMiLCAicGVyIENhcGl0YSIsICIoa2lsb3Rvbm5lcykiKSkNCg0KdGV4dDNEKHggPSBHRFBQQywgeSA9IEVQQywgeiA9IENPMlBDXzE5OTUsIA0KICAgICAgIGNleCA9IDAuNzUsDQogICAgICAgbGFiZWxzID0gZW5lcmd5X2FuZF9lbWlzc2lvbnMkQ29udGluZW50LA0KICAgICAgIGFkZCA9IFRSVUUpDQoNCnBsb3RyZ2woKQ0KYGBgDQoNCiMjIyBPdGhlciAzRCBvYmplY3RzDQoNClRoZXJlIGFyZSBvdGhlciB0eXBlcyBvZiBvYmplY3RzIChiZXNpZGVzIHBvaW50cyBhbmQgdGV4dCkgdGhhdCBjYW4gYmUgcGxvdHRlZCBpbiAzRCB1c2luZyBgcGxvdDNEYCwgaW5jbHVkaW5nOg0KDQotIDNEIGxpbmVzDQotIDNEIHJpYmJvbnMNCi0gM0Qgc2xpY2VzDQotIDNEIHN1cmZhY2VzDQoNCkNvbXB1dGUgdGhlIGxpbmVhciByZWdyZXNzaW9uICh6ID0gYXggKyBieSArIGQpDQpgYGB7cn0NCm1vZCA8LSBsbShDTzJQQ18xOTk1IH4gR0RQUEMgKyBFUEMpDQpzdW1tYXJ5KG1vZCkNCmBgYA0KDQpQcmVkaWN0IHZhbHVlcyBvbiByZWd1bGFyIHh5IGdyaWQNCmBgYHtyfQ0KZ3JpZC5saW5lcyA9IDQwDQpHRFBQQy5wcmVkIDwtIHNlcShtaW4oR0RQUEMpLCBtYXgoR0RQUEMpLCBsZW5ndGgub3V0ID0gZ3JpZC5saW5lcykNCkVQQy5wcmVkIDwtIHNlcShtaW4oRVBDKSwgbWF4KEVQQyksIGxlbmd0aC5vdXQgPSBncmlkLmxpbmVzKQ0KeHkgPC0gZXhwYW5kLmdyaWQoR0RQUEMgPSBHRFBQQy5wcmVkLCBFUEMgPSBFUEMucHJlZCkNCkNPMi5wcmVkIDwtIG1hdHJpeChwcmVkaWN0KG1vZCwgbmV3ZGF0YSA9IHh5KSwgDQogICAgICAgICAgICAgICAgIG5yb3cgPSBncmlkLmxpbmVzLCBuY29sID0gZ3JpZC5saW5lcykNCmBgYA0KDQpGaXR0ZWQgcG9pbnRzIGZvciBkcm9wbGluZXMgdG8gc3VyZmFjZQ0KYGBge3J9DQpmaXRwb2ludHMgPC0gcHJlZGljdChtb2QpDQpgYGANCg0KU2NhdHRlciBwbG90IHdpdGggcmVncmVzc2lvbiBwbGFuZQ0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgICAgcGNoID0gMjAsDQogICAgICAgICAgY2V4ID0gMS41LA0KICAgICAgICAgIHRoZXRhID0gMzAsDQogICAgICAgICAgcGhpID0gMjAsDQogICAgICAgICAgbGFiZWxzID0gcm93Lm5hbWVzKGVuZXJneV9hbmRfZW1pc3Npb25zKSwNCiAgICAgICAgICB4bGFiID0gIkdEUFBDIiwgeWxhYiA9ICJFUEMiLCB6bGFiID0gIkNPMiIsDQogICAgICAgICAgY2xhYiA9IGMoIkNPMiBFbWlzc2lvbnMiLCAicGVyIENhcGl0YSIsICIoa2lsb3Rvbm5lcykiKSwNCiAgICAgICAgICBzdXJmID0gbGlzdCh4ID0gR0RQUEMucHJlZCwgDQogICAgICAgICAgICAgICAgeSA9IEVQQy5wcmVkLCANCiAgICAgICAgICAgICAgICB6ID0gQ08yLnByZWQsICANCiAgICAgICAgICAgICAgICBmYWNldHMgPSBOQSwgDQogICAgICAgICAgICAgICAgZml0ID0gZml0cG9pbnRzKSkNCmBgYA0KDQpgYGB7cn0NCnNjYXR0ZXIzRCh4ID0gR0RQUEMsIHkgPSBFUEMsIHogPSBDTzJQQ18xOTk1LCANCiAgICAgICAgICBwY2ggPSAyMCwNCiAgICAgICAgICBjZXggPSAxLjUsDQogICAgICAgICAgdGhldGEgPSAzMCwNCiAgICAgICAgICBwaGkgPSAyMCwNCiAgICAgICAgICBsYWJlbHMgPSByb3cubmFtZXMoZW5lcmd5X2FuZF9lbWlzc2lvbnMpLA0KICAgICAgICAgIHhsYWIgPSAiR0RQUEMiLCB5bGFiID0gIkVQQyIsIHpsYWIgPSAiQ08yIiwNCiAgICAgICAgICBjbGFiID0gYygiQ08yIEVtaXNzaW9ucyIsICJwZXIgQ2FwaXRhIiwgIihraWxvdG9ubmVzKSIpLA0KICAgICAgICAgIHN1cmYgPSBsaXN0KHggPSBHRFBQQy5wcmVkLCANCiAgICAgICAgICAgICAgICB5ID0gRVBDLnByZWQsIA0KICAgICAgICAgICAgICAgIHogPSBDTzIucHJlZCwgIA0KICAgICAgICAgICAgICAgIGZhY2V0cyA9IE5BLCANCiAgICAgICAgICAgICAgICBmaXQgPSBmaXRwb2ludHMpKQ0KDQp0ZXh0M0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgY2V4ID0gMC43NSwNCiAgICAgICBsYWJlbHMgPSBlbmVyZ3lfYW5kX2VtaXNzaW9ucyRDb3VudHJ5LA0KICAgICAgIGFkZCA9IFRSVUUpDQoNCnBsb3RyZ2woKQ0KYGBgDQoNCiMjIEludGVyYWN0aXZlIDNEIHBsb3RzIHdpdGggYHBsb3RseWANCg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KY29sb3JzIDwtIGMoJyM0QUM2QjcnLCAnIzE5NzJBNCcsICcjOTY1RjhBJywgJyNGRjcwNzAnLCAnI0M2MTk1MScpDQoNCnBsb3RfbHkoZW5lcmd5X2FuZF9lbWlzc2lvbnMsIHggPSB+R0RQUEMsIHkgPSB+RVBDLCB6ID0gfkNPMlBDXzE5OTUsIGNvbG9yID0gfkNvbnRpbmVudCwgc2l6ZSA9IH5DTzJQQ18xOTk1LCBjb2xvcnMgPSBjb2xvcnMsDQogICAgICAgICAgICAgbWFya2VyID0gbGlzdChzeW1ib2wgPSAnY2lyY2xlJywgc2l6ZW1vZGUgPSAnZGlhbWV0ZXInLA0KICAgICAgICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KHdpZHRoID0gMiwgY29sb3IgPSAnIzAwMDAwMCcpKSwgDQogICAgICAgICAgICAgICAgICAgICAgc2l6ZXMgPSBjKDUsIDQwKSwNCiAgICAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCdDb3VudHJ5OicsIENvdW50cnksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj5HRFAgcGVyIGNhcGl0YTonLCByb3VuZChHRFBQQywgMyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj5FbmVyZ3kgQ29uc3VtcHRpb246Jywgcm91bmQoRVBDLDMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj5DTzIgZW1taXNzaW9ucyAxOTk1OicsICByb3VuZChDTzJQQ18xOTk1LCAzKSkpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnR0RQIHBlciBjYXBpdGEgdi4gRW5lcmd5IGNvbnN1bXB0aW9uIHBlciBjYXBpdGEnLA0KICAgICAgICAgc2NlbmUgPSBsaXN0KHhheGlzID0gbGlzdCh0aXRsZSA9ICdHRFAgcGVyIGNhcGl0YScsDQogICAgICAgICAgICAgICAgICAgICAgZ3JpZGNvbG9yID0gJ3JnYigyNTUsIDI1NSwgMjU1KScsDQogICAgICAgICAgICAgICAgICAgICAgemVyb2xpbmV3aWR0aCA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgdGlja2xlbiA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgZ3JpZHdpZHRoID0gMiksDQogICAgICAgICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAnRW5lcmd5IENvbnN1bXB0aW9uJywNCiAgICAgICAgICAgICAgICAgICAgICBncmlkY29sb3IgPSAncmdiKDI1NSwgMjU1LCAyNTUpJywNCiAgICAgICAgICAgICAgICAgICAgICB6ZXJvbGluZXdpZHRoID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICB0aWNrbGVuID0gNSwNCiAgICAgICAgICAgICAgICAgICAgICBncmlkd2l0aCA9IDIpLA0KICAgICAgICAgICAgICAgemF4aXMgPSBsaXN0KHRpdGxlID0gJ0NPMiBFbWlzc2lvbnMnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyaWRjb2xvciA9ICdyZ2IoMjU1LCAyNTUsIDI1NSknLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHplcm9saW5ld2lkdGggPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpY2tsZW4gPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyaWR3aXRoID0gMikpLA0KICAgICAgICAgcGFwZXJfYmdjb2xvciA9ICdyZ2IoMjQzLCAyNDMsIDI0MyknLA0KICAgICAgICAgcGxvdF9iZ2NvbG9yID0gJ3JnYigyNDMsIDI0MywgMjQzKScpDQpgYGANCg0KDQoNCg0KYGBge3J9DQpjbzJfOTV0bzE1IDwtIGVuZXJneV9hbmRfZW1pc3Npb25zICU+JSANCiAgZHBseXI6OnNlbGVjdChDb3VudHJ5LCBDb250aW5lbnQsIEdEUFBDLCBFUEMsIENPMlBDXzE5OTUsIENPMlBDXzIwMDUsIENPMlBDXzIwMTUpICU+JSAjIEZpcnN0IHNlbGVjdCByZWxldmFudCB2YXJpYWJsZXMNCiAgZ2F0aGVyKFllYXIsIENPMiwgLWMoQ291bnRyeSwgQ29udGluZW50LCBHRFBQQywgRVBDKSkgJT4lICMgR2F0aGVyIENPMiBjb2x1bW5zOiBpdCBpcyBpbXBvcnRhbnQgdG8gZXhjbHVkZSBmcm9tIHRoaXMgb3BlcmF0aW9uIHRoZSBjb2x1bW5zIENvdW50cnkgYW5kIEdEUA0KICBtdXRhdGUoWWVhciA9IGZhY3RvcihZZWFyLCANCiAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiQ08yUENfMTk5NSIsICJDTzJQQ18yMDA1IiwgIkNPMlBDXzIwMTUiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiMTk5NSIsICIyMDA1IiwgIjIwMTUiKSkpICMgUmVsYWJlbCB0aGUgeWVhcnMNCmBgYA0KDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBsb3RfbHkoY28yXzk1dG8xNSwgeCA9IH5HRFBQQywgeSA9IH5FUEMsIHogPSB+Q08yLCANCiAgICAgICAgY29sb3IgPSB+Q29udGluZW50LCANCiAgICAgICAgc2l6ZSA9IH5DTzIsIA0KICAgICAgICBjb2xvcnMgPSBjb2xvcnMsDQogICAgICAgIGZyYW1lID0gflllYXIsDQogICAgICAgIG1hcmtlciA9IGxpc3Qoc3ltYm9sID0gJ2NpcmNsZScsIHNpemVtb2RlID0gJ2RpYW1ldGVyJywNCiAgICAgICAgICAgICAgICAgICAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDIsIGNvbG9yID0gJyMwMDAwMDAnKSksIA0KICAgICAgICAgICAgICAgICAgICAgIHNpemVzID0gYyg1LCA0MCksDQogICAgICAgIHRleHQgPSB+cGFzdGUoJ0NvdW50cnk6JywgQ291bnRyeSwgDQogICAgICAgICAgICAgICAgICAgICAgJzxicj5HRFAgcGVyIGNhcGl0YTonLCByb3VuZChHRFBQQywgMyksIA0KICAgICAgICAgICAgICAgICAgICAgICc8YnI+RW5lcmd5IENvbnN1bXB0aW9uOicsIHJvdW5kKEVQQywzKSwNCiAgICAgICAgICAgICAgICAgICAgICAnPGJyPkNPMiBlbW1pc3Npb25zIDE5OTU6JywgIHJvdW5kKENPMlBDXzE5OTUsIDMpKSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICdHRFAgcGVyIGNhcGl0YSB2LiBFbmVyZ3kgY29uc3VtcHRpb24gcGVyIGNhcGl0YScsDQogICAgICAgICBzY2VuZSA9IGxpc3QoeGF4aXMgPSBsaXN0KHRpdGxlID0gJ0dEUCBwZXIgY2FwaXRhJywNCiAgICAgICAgICAgICAgICAgICAgICBncmlkY29sb3IgPSAncmdiKDI1NSwgMjU1LCAyNTUpJywNCiAgICAgICAgICAgICAgICAgICAgICB6ZXJvbGluZXdpZHRoID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICB0aWNrbGVuID0gNSwNCiAgICAgICAgICAgICAgICAgICAgICBncmlkd2lkdGggPSAyKSwNCiAgICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICdFbmVyZ3kgQ29uc3VtcHRpb24nLA0KICAgICAgICAgICAgICAgICAgICAgIGdyaWRjb2xvciA9ICdyZ2IoMjU1LCAyNTUsIDI1NSknLA0KICAgICAgICAgICAgICAgICAgICAgIHplcm9saW5ld2lkdGggPSAxLA0KICAgICAgICAgICAgICAgICAgICAgIHRpY2tsZW4gPSA1LA0KICAgICAgICAgICAgICAgICAgICAgIGdyaWR3aXRoID0gMiksDQogICAgICAgICAgICAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAnQ08yIEVtaXNzaW9ucycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JpZGNvbG9yID0gJ3JnYigyNTUsIDI1NSwgMjU1KScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFuZ2UgPSBjKDAsIDAuMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgemVyb2xpbmV3aWR0aCA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGlja2xlbiA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JpZHdpdGggPSAyKSksDQogICAgICAgICBwYXBlcl9iZ2NvbG9yID0gJ3JnYigyNDMsIDI0MywgMjQzKScsDQogICAgICAgICBwbG90X2JnY29sb3IgPSAncmdiKDI0MywgMjQzLCAyNDMpJykgJT4lIA0KICBhbmltYXRpb25fb3B0cygNCiAgICAxMzAwLCByZWRyYXcgPSBGQUxTRQ0KICApDQpgYGANCg==